package com.jcl.cloud.services.login.task;


import com.alibaba.fastjson.JSON;
import com.jcl.cloud.services.login.exception.CookieErrException;
import com.jcl.cloud.services.login.exception.LoginErrException;
import com.jcl.cloud.services.login.exception.OutTimesException;
import com.jcl.cloud.services.login.factory.WebDriverPool;
import com.jcl.cloud.services.login.model.InputModel;
import com.jcl.cloud.services.login.model.LoginModel;
import com.jcl.cloud.services.login.model.TaskStatusModel;
import com.jcl.cloud.services.login.service.RedisService;
import com.jcl.cloud.services.login.util.ImageUtil;
import com.jcl.cloud.services.login.util.SerializeUtils;
import com.jcl.cloud.services.login.util.StringUtil;
import com.jcl.cloud.services.login.util.yzm.DaMaUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.interactions.Actions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;

@Service
public class QcwyLoginTaskHandler implements LoginTaskHandler {
    private static Logger logger = LoggerFactory.getLogger(QcwyLoginTaskHandler.class);

    @Autowired
    private RedisService redisService;

    @Override
    public void execute(LoginModel loginModel) {
        try {
            reportStatus(1, loginModel);
            boolean isInputSucc = doInput(loginModel);
            if (isInputSucc) {
                clickVCodeBtn(loginModel);
            }

            reportStatus(2, loginModel);
            boolean isVCodeSucc = handleVCode(loginModel);
            if (isVCodeSucc) {
                //报告状态，验证码验证成功,验证失败会抛异常
                reportStatus(3, loginModel);
            }

            reportStatus(4, loginModel);
            boolean isLoginSucc = handleLogin(loginModel);
            if (isLoginSucc) {
                //登录成功
                reportStatus(5, loginModel);


                //上传cookies
                handleCookies(loginModel);
                reportStatus(6, loginModel);


                //判断任务是否需要进行验证码验证解锁
                String isNeedDeLockStr = this.redisService.hget("unicrawler:task:info:" + loginModel.getCrawlerId(), "isNeedDeLock");
                if ("true".equalsIgnoreCase(isNeedDeLockStr)) {
                    //需要解锁
                    this.deLock(loginModel);
                }


                this.redisService.hset("unicrawler:task:info:" + loginModel.getCrawlerId(), "isNeedDeLock", "false");
                //登录成功,将任务重新放回队列
                this.redisService.lpush("unicrawler:tasks:all", loginModel.getCrawlerId());
            } else {
                //清楚账户信息、爬虫登录元数据
                String accountKey = this.redisService.hget("unicrawler:task:info:" + loginModel.getCrawlerId(), "accountKey");
                this.redisService.hdel("usermgr:account-crawler:map",accountKey);
                this.redisService.del("unicrawler:task:info:" + loginModel.getCrawlerId());
            }
        } catch (LoginErrException loee) {
            logger.error(String.format("任务号：%s    执行发生LoginErrException异常：%s", loginModel.getCrawlerId(), loee.getMessage()), loee);
            loginModel.setLoginErrMsg(loee.getMessage());
            reportStatus(7, loginModel);
        } catch (OutTimesException orte) {
            logger.error(String.format("任务号：%s    执行发生OverTimesException异常：%s", loginModel.getCrawlerId(), orte.getMessage()), orte);

            loginModel.setLoginErrMsg(orte.getMessage());
            reportStatus(8, loginModel);
        } catch (CookieErrException cee) {
            logger.error(String.format("任务号：%s    执行发生CookieErrException异常：%s", loginModel.getCrawlerId(), cee.getMessage()), cee);

            loginModel.setLoginErrMsg(cee.getMessage());
            reportStatus(9, loginModel);
        } catch (Exception e) {
            logger.error(String.format("任务号：%s    执行发生Exception异常：%s", loginModel.getCrawlerId(), e.getMessage()), e);

            loginModel.setLoginErrMsg("系统异常");
            reportStatus(10, loginModel);
        } finally {
            try {
                //CustomJedisPool.returnResource(jedis);
                WebDriverPool.getInstance().returnObject("CHROME", loginModel.getDriver());
            } catch (Exception e) {
                logger.error(String.format("任务号：%s    资源归还发生异常：%s", loginModel.getCrawlerId(), e.getMessage()), e);
            }
        }

    }


    private boolean doInput(LoginModel loginModel) throws Exception {
        WebDriver driver = loginModel.getDriver();
        String taskId = loginModel.getCrawlerId();
        driver.get("https://ehire.51job.com/");
        boolean flag = false;

        JavascriptExecutor executor = (JavascriptExecutor) driver;

        List list = this.redisService.hmget("unicrawler:task:info:" + loginModel.getCrawlerId(), "account", "userName", "passWord");
        //填写用户登录信息
        executor.executeScript("var q = document.getElementById(\"txtMemberNameCN\").value=" + "\"" + list.get(0) + "\"");
        executor.executeScript("var q = document.getElementById(\"txtUserNameCN\").value=" + "\"" + list.get(1) + "\"");
        executor.executeScript("var q = document.getElementById(\"txtPasswordCN\").value=" + "\"" + list.get(2) + "\"");
        flag = true;
        logger.info(String.format("任务号：%s   输入用户信息成功", taskId));

        return flag;
    }


    private boolean clickVCodeBtn(LoginModel loginModel) throws Exception {
        boolean flag = false;
        WebDriver driver = loginModel.getDriver();
        WebElement btnBeginValidate = ((ChromeDriver) driver).findElementById("btnBeginValidate");
        btnBeginValidate.click();
        Thread.sleep(1000);
        flag = true;
        logger.info(String.format("任务号：%s   点击验证码按钮", loginModel.getCrawlerId()));

        return flag;
    }


    private byte[] getVCodeImage(LoginModel loginModel) throws Exception {
        WebDriver driver = loginModel.getDriver();
        byte[] imgCode = new byte[0];
        int times = 0;
        do {
            times++;
            try {

                File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
                FileUtils.copyFile(scrFile, new File("/app/jcl-crawler/images/" + loginModel.getCrawlerId() + "_" + times + "_qcwy_截取验证码之前.png"));


                WebElement yz_main = ((ChromeDriver) driver).findElementByClassName("yz-main");
                //File scrFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);

                Point p = yz_main.getLocation();
                int width = yz_main.getSize().getWidth();
                int higth = yz_main.getSize().getHeight();
                BufferedImage img = ImageIO.read(scrFile);
                BufferedImage dest = img.getSubimage(p.getX(), p.getY(), width, higth);

                //ImageIO.write(dest, "png", scrFile);

                ByteArrayOutputStream out = new ByteArrayOutputStream();
                ImageIO.write(dest, "png", out);

                imgCode = ImageUtil.compressPic(out.toByteArray(), 0.5f);
                break;
            } catch (Exception e) {
                Thread.sleep(5000);
                logger.error("前程无忧  获取验证码发生异常：" + e.getMessage(), e);
                if (times > 6) {
                    throw e;
                }
            }
        } while (true);

        return imgCode;
    }


    private boolean handleVCode(LoginModel loginModel) throws Exception {
        boolean flag = false;
        int times = 0;
        WebDriver driver = loginModel.getDriver();
        do {
            times++;

            byte[] code = getVCodeImage(loginModel);

            logger.info(String.format("任务号：%s 验证码图片大小：%s", loginModel.getCrawlerId(), (code.length * 4 / 1024) + "K"));
            String daMaResult = DaMaUtil.recognizeImage(code);
            logger.info(String.format("任务号：%s    打码平台获取到的验证码结果：%s", loginModel.getCrawlerId(), daMaResult));
            //处理验证码
            String pointVal = StringUtil.getStrByRgex(daMaResult, "<Result>(.*?)</Result>");

            String daMaId = StringUtil.getStrByRgex(daMaResult, "<Id>(.*?)</Id>");
            if (StringUtils.isNotBlank(pointVal)) {
                String[] pArr = pointVal.replaceAll(" ", "").split("\\.");
                String[] pBak = {};
                //通过坐标点击汉字
                WebElement yz_main = ((ChromeDriver) driver).findElementByClassName("yz-main");
                Actions action = new Actions(driver);

                for (String pStr : pArr) {
                    pBak = pStr.split(",");
                    action.moveToElement(yz_main, Integer.parseInt(pBak[0]), Integer.parseInt(pBak[1])).click().perform();
                    Thread.sleep(100);
                }

                ((ChromeDriver) driver).findElementById("btnValidate").click();
                //查看验证码是否通过校验
                boolean isVCodeSucc = checkVCode(loginModel);
                if (isVCodeSucc) {
                    logger.info(String.format("任务号：%s    验证码验证通过", loginModel.getCrawlerId()));
                    flag = true;
                    break;
                } else {
                    logger.error(String.format("任务号：%s    验证码验证失败，重试次数times：%s", loginModel.getCrawlerId(), times));

                    if (times > 10) {
                        throw new OutTimesException("验证码模拟验证重试shi次失败");
                    }
                    //上报打码平台验证码错误
                    String result = DaMaUtil.reportErr(daMaId);
                    logger.info(String.format("任务号：%s  打码Id：%s   上报打码坐标错误返回的结果：%s", loginModel.getCrawlerId(), daMaId, result));

                    Thread.sleep(1000);
                    continue;
                }
            } else {
                logger.info(String.format("任务号：%s   打码平台返回异常，重新打码", loginModel.getCrawlerId()));
                Thread.sleep(1000);

                if (times > 3) {
                    throw new OutTimesException("验证码打码重试三次失败");
                }
                continue;
            }

        } while (true);

        return flag;
    }


    private boolean handleLogin(LoginModel loginModel) throws Exception {
        boolean flag = false;
        String source = "";
        int times = 0;
        WebDriver driver = loginModel.getDriver();
        String taskId = loginModel.getCrawlerId();

        ((ChromeDriver) driver).findElementById("Login_btnLoginCN").click();
        do {
            times++;
            if (times > 6) {
                throw new OutTimesException("点击登录按钮后，页面等待发生超时");
            }
            source = driver.getPageSource();

            if (source.contains("会员名、用户名或密码不准确")) {
                logger.info(String.format("任务号：%s   会员名、用户名或密码不准确", taskId));
                flag = false;
                loginModel.setLoginErrMsg("会员名、用户名或密码不准确!");
                break;
            }

            if (source.contains("强制下线")) {
                WebElement KickOut = driver.findElement(By.xpath("//a[contains(text(),'强制下线')]"));
                logger.info("系统检测到，您已通过其他浏览器登录到本系统；或者是您上次登录后没有正常退出！");
                KickOut.click();

                Thread.sleep(1000);
                source = driver.getPageSource();

                if (source.contains("强制下线")) {
                    Thread.sleep(3000);
                    source = driver.getPageSource();
                    if (source.contains("查看简历")) {
                        logger.info(String.format("任务号：%s  点击强制下线后,等待3秒系统登录成功", taskId));
                        flag = true;
                        break;
                    } else {
                        throw new LoginErrException("点击强制下线后,等待3秒页面未跳转成功");
                    }
                } else if (source.contains("查看简历")) {
                    logger.info(String.format("任务号：%s  点击强制下线后系统登录成功", taskId));
                    flag = true;
                    break;
                } else if (source.contains("企业会员登录")) {
                    logger.info(String.format("任务号：%s  点击强制下线后系统返回到登录页面", taskId));

                    Thread.sleep(3000);
                    //需要重试
                    this.execute(loginModel);


                    //throw new LoginErrException("点击强制下线后系统返回到登录页面");
                }
            } else if (source.contains("查看简历")) {
                logger.info(String.format("任务号：%s  系统登录成功", loginModel.getCrawlerId()));
                flag = true;
                break;
            } else {
                logger.info(String.format("任务号：%s  等待次数times:%s  页面未跳转，系统等待中...", taskId, times));
                Thread.sleep(1000);
            }
        } while (true);

        /*//点击查看简历按钮
        driver.get("https://ehire.51job.com/InboxResume/InboxRecentEngine.aspx");
        Thread.sleep(1000);*/
        return flag;
    }


    private void handleCookies(LoginModel loginModel) throws Exception {
        WebDriver driver = loginModel.getDriver();

        String taskId = loginModel.getCrawlerId();
        HttpClientContext context = null;
        BasicCookieStore cookieStore = null;
        try {
            cookieStore = new BasicCookieStore();
            context = HttpClientContext.create();
            Set<Cookie> cookieSet = driver.manage().getCookies();

            for (Cookie c : cookieSet) {
                BasicClientCookie cookie = new BasicClientCookie(c.getName(), c.getValue());
                cookie.setDomain(c.getDomain());
                cookie.setPath(c.getPath());
                cookie.setExpiryDate(c.getExpiry());
                cookieStore.addCookie(cookie);
            }
            context.setCookieStore(cookieStore);
        } catch (Exception e) {
            logger.error(String.format("任务号：%s    获取登录cookie发生异常：%s", taskId, e.getMessage()), e);
            throw new CookieErrException("获取登录Cookie发生异常");
        }


        String cookieStoreStr = SerializeUtils.serializeToStr(cookieStore);


        logger.info(String.format("任务号：%s   上传的登录cookie信息：%s", taskId, cookieStoreStr));
        this.redisService.hset("unicrawler:task:info:" + taskId, "cookieStore", cookieStoreStr);
    }


    private boolean checkVCode(LoginModel loginModel) throws Exception {
        WebDriver driver = loginModel.getDriver();
        String taskId = loginModel.getCrawlerId();
        boolean flag = false;
        String conditionA = "点击完成验证";
        String conditionC1 = "<a class=\"yz-btn yz-btn-on\" style=\"display:none;\" id=\"btnEndValidate\">";
        String conditionC2 = "<a class=\"yz-btn yz-btn-on\" style=\"\" id=\"btnEndValidate\">";
        String conditionD1 = "<div class=\"yz-down\" id=\"divValidateHtml\" style=\"\">";
        String conditionD2 = "<div class=\"yz-down\" id=\"divValidateHtml\" style=\"display: none;\">";
        String source = "";

        source = driver.getPageSource();
        if (source.contains(conditionA)) {
            logger.info(String.format("任务号：%s  上一步模拟点击验证码发生异常", taskId));
        } else if (source.contains(conditionC1) && source.contains(conditionD1)) {
            logger.info(String.format("任务号：%s  模拟点击验证码未完成，休息一秒", taskId));
            Thread.sleep(1000);
            source = driver.getPageSource();
            if (source.contains(conditionC2) && source.contains(conditionD2)) {
                flag = true;
                logger.info(String.format("任务号：%s  休息一秒后验证模拟点击验证码成功", taskId));
            } else {
                flag = false;
                logger.info(String.format("任务号：%s  休息一秒后验证模拟点击验证码失败", taskId));
            }
        } else if (source.contains(conditionC2) && source.contains(conditionD2)) {
            flag = true;
            logger.info(String.format("任务号：%s  模拟点击验证码成功", taskId));
        }
        return flag;
    }


    private void reportStatus(int step, LoginModel loginModel) {


        TaskStatusModel tsm = new TaskStatusModel();
        InputModel input = new InputModel();
        if (step == 1) {
            tsm.setDescription("登录信息准备中");
            tsm.setFinished(false);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DOING");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("");
        }

        if (step == 2) {
            tsm.setDescription("验证码验证中");
            tsm.setFinished(false);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DOING");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("");
        }


        if (step == 3) {
            tsm.setDescription("验证码验证成功");
            tsm.setFinished(false);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DOING");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("");
        }


        if (step == 4) {
            tsm.setDescription("登录中");
            tsm.setFinished(false);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DOING");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("");
        }


        if (step == 5) {
            tsm.setDescription("登录成功");
            tsm.setFinished(true);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DONE_SUCC");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("");
        }

        if (step == 6) {
            tsm.setDescription("登录cookie获取成功");
            tsm.setFinished(true);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DONE_SUCC");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("");
        }


        if (step == 7) {
            tsm.setDescription(loginModel.getLoginErrMsg());
            tsm.setFinished(true);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DONE_FAIL");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("407");
        }


        if (step == 8) {
            tsm.setDescription(loginModel.getLoginErrMsg());
            tsm.setFinished(true);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DONE_FAIL");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("408");
        }

        if (step == 9) {
            tsm.setDescription(loginModel.getLoginErrMsg());
            tsm.setFinished(true);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DONE_FAIL");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("409");
        }

        if (step == 10) {
            tsm.setDescription(loginModel.getLoginErrMsg());
            tsm.setFinished(true);
            tsm.setInput(input);
            tsm.setPhase("LOGIN");
            tsm.setPhase_status("DONE_FAIL");
            tsm.setTask_id(loginModel.getCrawlerId());
            tsm.setError_code("410");
        }

        String msg = JSON.toJSONString(tsm);
        logger.info(String.format("任务号：%s    上传的状态msg：%s", loginModel.getCrawlerId(), msg));
        this.redisService.hset("unicrawler:task:info:" + loginModel.getCrawlerId(), "status", msg);
    }


    private void deLock(LoginModel loginModel) {

        int times = 0;
        boolean flag = false;
        try {
            WebDriver driver = loginModel.getDriver();
            List<String> urlList = null;


            do {
                times++;
                driver.navigate().to("https://ehire.51job.com/Candidate/SearchResumeNew.aspx");
                Thread.sleep(10000);
                //点击第一个简历，到达解封页面

                String firstHandle = driver.getWindowHandle();


                driver.findElement(By.xpath("//tr[@id='trBaseInfo_1']/td[2]/span/a")).click();


                Thread.sleep(10000);

                String secondHandle = null;
                Set<String> handles = driver.getWindowHandles();
                for (String h : handles) {
                    if (!h.equalsIgnoreCase(firstHandle)) {
                        secondHandle = h;
                        break;
                    }
                }

                driver.close();
                driver = driver.switchTo().window(secondHandle);

                try {
                    WebElement yz_main = driver.findElement(By.className("yz-main"));
                    String yz_mainText = yz_main.getText();
                    logger.info("yz_main  的文本信息为：" + yz_mainText);
                    break;

                } catch (Exception e) {
                    logger.error("查找yz_main元素发生异常：" + e.getMessage(), e);


                    if (times > 10) {
                        logger.info("前程无忧解锁过程中发生重试异常");
                        break;
                    }
                }

            } while (true);


            Thread.sleep(10000);

            do {

                //判断是否需要点击验证码解锁
                WebElement title = driver.findElement(By.tagName("title"));
                String titleText = title.getText();
                logger.info("title中的内容为：" + titleText);
                //WebElement divVerifyCode_ch = driver.findElement(By.id("divVerifyCode_ch"));
                //String style = divVerifyCode_ch.getAttribute("style");
                WebElement __VIEWSTATE = driver.findElement(By.id("__VIEWSTATE"));
                String __VIEWSTATEValue = __VIEWSTATE.getAttribute("value");
                logger.info(String.format("__VIEWSTATE中的value属性：%s     长度：%d", __VIEWSTATEValue, __VIEWSTATEValue.length()));

                if (!titleText.contains("简历ID") && StringUtils.isBlank(titleText) && __VIEWSTATEValue.length() < 600) {
                    logger.info(String.format("任务号：%s    开始解锁>>>", loginModel.getCrawlerId()));


                    byte[] code = getVCodeImage(loginModel);

                    logger.info(String.format("任务号：%s 验证码图片大小：%s", loginModel.getCrawlerId(), (code.length * 4 / 1024) + "K"));
                    String daMaResult = DaMaUtil.recognizeImage(code);
                    logger.info(String.format("任务号：%s    打码平台获取到的验证码结果：%s", loginModel.getCrawlerId(), daMaResult));
                    //处理验证码
                    String pointVal = StringUtil.getStrByRgex(daMaResult, "<Result>(.*?)</Result>");

                    String daMaId = StringUtil.getStrByRgex(daMaResult, "<Id>(.*?)</Id>");
                    if (StringUtils.isNotBlank(pointVal)) {
                        String[] pArr = pointVal.replaceAll(" ", "").split("\\.");
                        String[] pBak = {};
                        //通过坐标点击汉字
                        WebElement yz_main = ((ChromeDriver) driver).findElementByClassName("yz-main");
                        Actions action = new Actions(driver);

                        for (String pStr : pArr) {
                            pBak = pStr.split(",");
                            action.moveToElement(yz_main, Integer.parseInt(pBak[0]), Integer.parseInt(pBak[1])).click().perform();
                            Thread.sleep(100);
                        }

                        ((ChromeDriver) driver).findElementById("btnValidate").click();


                        //查看验证码是否通过校验
                        boolean isVCodeSucc = checkDelockVCode(loginModel);
                        if (isVCodeSucc) {
                            logger.info(String.format("任务号：%s    验证码解封验证通过", loginModel.getCrawlerId()));
                            //flag = true;
                            break;
                        } else {
                            logger.error(String.format("任务号：%s    验证码解封验证失败，重试次数times：%s", loginModel.getCrawlerId(), times));

                            if (times > 10) {
                                throw new OutTimesException("验证码解封模拟验证重试shi次失败");
                            }
                            //上报打码平台验证码错误
                            String result = DaMaUtil.reportErr(daMaId);
                            logger.info(String.format("任务号：%s  打码Id：%s   上报打码坐标错误返回的结果：%s", loginModel.getCrawlerId(), daMaId, result));

                            Thread.sleep(1000);
                            continue;
                        }
                    } else {
                        logger.info(String.format("任务号：%s   打码平台返回异常，重新打码", loginModel.getCrawlerId()));
                        Thread.sleep(1000);

                        if (times > 3) {
                            throw new OutTimesException("验证码打码重试三次失败");
                        }
                        continue;
                    }

                } else {
                    logger.info(String.format("任务号：%s  不需要解锁", loginModel.getCrawlerId()));
                    break;
                }
            } while (true);

        } catch (Exception e) {
            logger.error(String.format("任务号：%s   解锁发生异常：%s", loginModel.getCrawlerId(), e.getMessage()), e);
        }


    }


    private boolean checkDelockVCode(LoginModel loginModel) {
        WebDriver driver = loginModel.getDriver();
        boolean flag = false;
        try {
            Thread.sleep(20000);
            WebElement title = driver.findElement(By.tagName("title"));
            logger.info("title中的内容为：" + title.getText());
            if (title.getText().contains("简历ID")) {
                flag = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return flag;
    }

}
